/*Autores:
	Henrique de Oliveira Buzachero 	nUSP 6766242
	Rafael Rodrigues de Souza	nUSP 6872279
um shell para linux
*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>	/*strtok()*/
#include <unistd.h>	/*execv(), fork(), read(), write(), close()*/
#include <errno.h>
#include <sys/types.h>	/*getpid(), open()*/
#include <sys/stat.h>	/*open()*/
#include <fcntl.h>	/*open()*/
#include <sys/wait.h>	/*wait()*/
#include <signal.h>
#include <stddef.h>
#include "utils.h"

#define MAX_COMAND 2048
#define DELIMITADORES " \t\n"
#define DELIMITADORPIPE "|"
#define MAX_N_CHILDREN 10
#define MAX_LENGTH_PATH 500

typedef struct info {
    pid_t ID;
    int status;
    char name[MAX_COMAND];
} PROCESS_INFO;

PROCESS_INFO *p_info;
int count_child = 0;

int controlPipe(char*, char*);


int execPipe(char**, int, char *);

/*retorna 0 se eh commando interno
ou > 0 caso contrario*/
int exec_command_intern(char*);

void suspend();

void terminate();

void refreshChildren();

void printChildren();

int main(int argc, char* argv[]){

    char **args = NULL;
	char *p = NULL, *aux = NULL;
	char *output = NULL, *input = NULL, *erro = NULL;
	char *command = NULL; /*mudar para dinamico*/
    int pid, i, n = 0, flag;
	int fd0, fd1, fd2;
	int status; /*retorno do processo filho*/
	int id_command;
	int aux2;
    char *process_name;
    char *path, *path_cp, *pointer_aux;
    int size_path;
    struct sigaction act_stp, act_end, act_chld;
    sigset_t new_mask, old_mask;

    if(!memset(&act_stp, 0, sizeof(struct sigaction)))
        fatal();

    if(!memset(&act_end, 0, sizeof(struct sigaction)))
        fatal();

    if(!memset(&act_chld, 0, sizeof(struct sigaction)))
        fatal();

    sigemptyset(&new_mask);
    sigaddset(&new_mask, SIGTSTP);
    sigaddset(&new_mask, SIGINT);
    sigprocmask(SIG_SETMASK, &new_mask, &old_mask);

    act_stp.sa_handler = suspend;
    act_end.sa_handler = terminate;
    act_chld.sa_handler = refreshChildren;

    if(sigaction(SIGTSTP, &act_stp, NULL))
        fatal();

    if(sigaction(SIGINT, &act_end, NULL))
        fatal();

    if(sigaction(SIGCHLD, &act_chld, NULL))
        fatal();

	/*commando de no max 2048 caracteres*/
	command = (char*) malloc(sizeof(char)*(MAX_COMAND+1));
	process_name = malloc(sizeof(char)*(MAX_COMAND));
    p_info = malloc(MAX_N_CHILDREN*sizeof(PROCESS_INFO));
    size_path = strlen(getenv("PATH"));
    path = malloc(size_path*sizeof(char));
    strcpy(path, (char *)getenv("PATH"));
    //printf("PATH = %s\n", path);

	while(1){

		n = 0;
		printf("shell $: ");
		fgets(command, MAX_COMAND, stdin); /*Lendo o commando*/
        strcpy(process_name, command);
        process_name[strlen(command)-1] = '\0';

		/*verificando se tem pipes*/
        if(controlPipe(command, path) == 0) {

			/*se nao houver pipes*/
			p = strtok(command, DELIMITADORES);
			if(p != NULL){
			do{
                /*verificando se ha algum tipo de redirecionamento*/
				if(strcmp(p, "<") == 0){
					p = strtok(NULL, DELIMITADORES);
					/*redirecionar entrada*/
					input = (char*)malloc (sizeof(char)*strlen(p));
					strcpy(input, p);

				}
				else if(strcmp(p, ">") == 0){
                    			p = strtok(NULL, DELIMITADORES);
					/*redirecionar saida*/
					output = (char*)malloc (sizeof(char)*strlen(p));
					strcpy(output, p);
					flag = 1;

				}
				else if(strcmp(p, ">>") == 0){
					p = strtok(NULL, DELIMITADORES);
					/*redirecionar saida no apendice do arquivo*/
					output = (char*)malloc (sizeof(char)*strlen(p));
					strcpy(output, p);
					flag = 0;

				}
				else if(strcmp(p, "2>") == 0){
					p = strtok(NULL, DELIMITADORES);
					/*redirecionar saida no apendice do arquivo*/
					erro = (char*)malloc (sizeof(char)*strlen(p));
					strcpy(erro, p);

				}
				else{
					/*armazena o commando*/
					args = (char**) realloc(args, (++n)*(sizeof(char*)));
					args[n-1] = (char*) malloc(sizeof(char)*(strlen(p)+1));
					strcpy(args[n-1], p);
				}
				p =(char*) strtok(NULL, DELIMITADORES);/*busca o proximo commando*/

			} while(p != NULL);/*retorna NULL se acabou os commandos*/

			args = (char**) realloc(args, (++n)*(sizeof(char*)));
			args[n-1] = NULL;/*o ultimo deve ser NULL*/

			/*se o primeiro commando for exit, o shell finaliza */
			if(strcmp("exit", args[0]) == 0){
				printf("good bye...  \n");
				/*Libera a memoria*/
				for(i = 0; i < n; i++)
					free(args[i]);
				free(args);
				free(command);
				return(0);
			}

            id_command = exec_command_intern(command);
            if(!id_command) { /*verifica se eh commando interno*/
                pid = fork();/*criando os processo pai e filho*/

                if(pid < 0){ printf("Falha no shell\n"); }

		        else if(pid > 0){ /*PROCESSO PAI*/
                    p_info[count_child].ID = pid;
                    p_info[count_child].status = 0;
                    strcpy(p_info[count_child].name, process_name);
                    count_child++;
                    wait(&status);
                    //sleep(4);
		    	/*Limpando toda a memoria */
				for(i = 0; i < n; i++)
						free(args[i]);
				if(args != NULL)
					free(args);
				if(input != NULL){ free(input); input = NULL; }
				if(output != NULL){ free(output); output = NULL; }
				if(erro != NULL){ free(erro); erro = NULL; }
				args = NULL;
		}/*fim else if*/

                else{	/*PROCESSO FILHO*/
                    sigprocmask(SIG_UNBLOCK, &new_mask, &old_mask);
		    		/*fazendo os redirecionamento de entrada e saida*/
					if(input != NULL){
						/*redirecionamento de entrada*/
						close(0);
						fd0 = open(input, O_RDONLY);
						if(fd0 < 0) { exit(1); }
					}

					if(output != NULL){
						close(1);
						/*redirecionamente de saida*/
					    if(flag == 1){
							/*saida padrao > */
							fd1 = open(output, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR );
							if(fd1 < 0) { exit(1); }
						}
						else{
							/*saida de padrao como apendice >>*/
							fd1 = open(output, O_CREAT | O_RDWR | O_APPEND, S_IRUSR | S_IWUSR);
							if(fd1 < 0) { exit(1); }
						}
					}
					if(erro != NULL){
						/*redirecionamento da saida de erro*/
						close(2);
						fd2 = open(erro, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
						if(fd2 < 0) { exit(1); }
					}
						/*fim do redirecionamento*/

					//aux = malloc(sizeof(char)*(strlen(args[0])+10));
					/*verifica se eh algum commando que se encontra em /bin/ ou /usr/bin*/
					if(strncmp("./", command, 2) != 0){/*se o commando nao comeca com ./ entao deve estar em um desses diretorios*/
                        path_cp = strdup(path);
                        pointer_aux = strtok(path_cp, ":");
                        while(pointer_aux != NULL) {
                            //printf("p1 = %s\n", pointer_aux);
                            if(pointer_aux[0] == ':') {
                                pointer_aux++;
                                pointer_aux = strtok(NULL, ":");
                            }
                            aux = malloc((strlen(pointer_aux)+strlen(args[0]))*sizeof(char));
						    sprintf(aux,"%s/%s", pointer_aux, args[0]);
						    execv(aux, args);
                            pointer_aux = strtok(NULL, ":");
                            free(aux);
                            aux = NULL;
                        }
                        free(path_cp);
                        path_cp = NULL;
                        //free
					}/*fim if*/
					/*se nao for nenhum commando em /bin e /usr/bin, tenta executa normalmente*/
					execv(args[0], args); /*executando*/
					printf("%s: %s\n", args[0], strerror(errno));/*Se retornou, significa que houve erro*/
					return EXIT_FAILURE;
				}/*fim else*/
			}/*fim if externo*/
            else {
                switch(id_command) {
                    case 1:
                        if(!chdir(args[1])) {
                            printf("The current directory changed to %s\n", args[1]);
                        } else {
                            printf("Failed at changing the directory.\n");
                        }
                        break;
                    case 2:
                        if(count_child != 0) {
                            printChildren();
                        } else {
                            printf("There are no child that was stopped.\n");
                        }
                        break;
                    case 3:
                        if(args[1] != NULL) {
                            aux2 = atoi(args[1]);
                        }
                        else {
                            aux2 = count_child-1;
                        }

                        if((count_child != 0) && (aux2 >= 0) && (aux2 < count_child)) {
                            kill(p_info[aux2].ID, SIGCONT);
                            p_info[aux2].status = 0;
                        } else if(count_child == 0){
                            printf("There are no child that was stopped.\n");
                        } else {
                            printf("Argument invalid.\n");
                        }
                        break;
                    case 4:
                        if(args[1] != NULL) {
                            aux2 = atoi(args[1]);
                        }
                        else {
                            aux2 = count_child-1;
                        }

                        if((count_child != 0) && (aux2 >= 0) && (aux2 < count_child)) {
                            kill(p_info[aux2].ID, SIGCONT);
                            p_info[aux2].status = 0;
                            wait(&status);
                        } else if(count_child == 0){
                            printf("There are no child that was stopped.\n");
                        } else {
                            printf("Argument invalid.\n");
                        }
                        break;
                }

            }
		}/*if p!=NULL*/
		}
	}/*fim while*/
}/*fim main*/

int exec_command_intern(char* command){
    if(strcmp(command, "cd") == 0)
        return 1;
    else if(strcmp(command, "jobs") == 0)
        return 2;
    else if(strcmp(command, "bg") == 0)
        return 3;
    else if(strcmp(command, "fg") == 0)
        return 4;
    else
        return 0;
}

void suspend() {
    printf("\nProcess number %d stopped!\n", getpid());
    signal(SIGINT, SIG_IGN);
    signal(SIGCHLD, SIG_IGN);
    raise(SIGSTOP);
    signal(SIGINT, terminate);
    signal(SIGCHLD, refreshChildren);
    printf("Returning to the process number %d...\n", getpid());
}

void terminate() {
    printf("\nKilling job number %d\n", getpid());
    //signal(SIGTSTP, SIG_IGN);
    //signal(SIGCHLD, SIG_IGN);
    raise(SIGTERM);
    //signal(SIGTSTP, suspend);
    //signal(SIGCHLD, refreshChildren);
}

void refreshChildren() {
    unsigned register int i;
    int status, pos;
    pid_t child_pid;

    signal(SIGINT, SIG_IGN);
    signal(SIGTSTP, SIG_IGN);
    child_pid = waitpid(WAIT_ANY, &status, WUNTRACED);
    for(i = 0; i < count_child; i++) {
        if(p_info[i].ID == child_pid) {
            pos = i;
            break;
        }
    }
        //printf("wifstp = %d, wifsig = %d, wifexit = %d\n", WIFSTOPPED(status), WIFSIGNALED(status), WIFEXITED(status));
    if(WIFSTOPPED(status)) {
        p_info[pos].status = 1;
        //printf("oaisubhdfkabskfvhdsj");
    } else if(WIFSIGNALED(status) || WIFEXITED(status)) {
        //printf("oaisubhdfkabskfvhdsj");
        for(i = pos; i < count_child; i++){
            p_info[i] = p_info[i+1];
        }
        count_child--;
    }

    signal(SIGTSTP, suspend);
    signal(SIGINT, terminate);
}

void printChildren() {
    unsigned register int i;
    char run[] = {"Running"};
    char stp[] = {"Stopped"};

    for(i = 0; i < count_child; i++) {
        if(p_info[i].status) {
            printf("[%d]:\t%s  -  %s\t(%d)\n", i, stp, p_info[i].name, p_info[i].ID);
        }
        else if(!p_info[i].status) {
            printf("[%d]:\t%s  -  %s\t(%d)\n", i, run, p_info[i].name, p_info[i].ID);
        }
    }
}


int controlPipe(char *command, char *path){
	char **args = NULL, *p = NULL;
	int n = 0, size, pid, status, i;

	size = strlen(command);
	p = strtok(command, DELIMITADORPIPE);
	if(strlen(p) == size){ return 0; }/*se o tamanho forem iguais entao nao ha nenhum pipe na linha de commando*/

	/*separando cada commando*/
	while(p != NULL){
		args = (char**) realloc(args, (++n)*sizeof(char*));
		args[n-1] = (char*) malloc(sizeof(char)*strlen(p));
		strcpy(args[n-1], p);
		p = strtok(NULL, DELIMITADORPIPE);
	}

	pid = fork();
	/*processo pai espera o filho terminar enquanto que o filho executa os commandos*/
	if(pid > 0){
		wait(&status);
		/*libera a memoria*/
		for(i = 0; i < n; i++){
			free(args[i]);
		}
		free(args);
		return 1;
	}

	else{
		execPipe(args, n-1, path);
		return -1;
	}
	return 1;
}

int execPipe(char** args, int n, char *path){
	int pid, size = 0;
	int pipefd[2], stat, status;
	char *p = NULL, **arg = NULL;
    char *path_cp, *aux = NULL, *pointer_aux;

	/*condicao de parada da recursao*/
	if(n != -1){
		/*separando os commando e argumento*/
		p = strtok(args[n], DELIMITADORES);
		while(p != NULL){
			arg = (char**)realloc(arg, sizeof(char*)*(++size));
			arg[size-1] = (char*)malloc(sizeof(char)*(strlen(p)+1));
			strcpy(arg[size-1], p);
			p = strtok(NULL, DELIMITADORES);
		}
		arg = (char**)realloc(arg, sizeof(char*)*(size+1));
		arg[size] = NULL;

		stat = pipe(pipefd);/*criando o pipe*/

		if(stat < 0){ exit(0); }
//		if(n != 0)
        pid = fork();

		if(pid > 0){ /*PAI*/
			/*somente o primeiro nao redireciona a entrada*/
			if(n != 0){
				close(0);
				dup(pipefd[0]);
				close(pipefd[0]);
				close(pipefd[1]);
				/*espera o filho terminar*/
				wait(&status);
			}
				/*verifica se o commando esta em /bin/ ou /usr/bin*/
				/*aux = (char*) malloc(sizeof(char)*(strlen(arg[0])+10));
				if(strncmp("./",arg[0] , 2) != 0){
					sprintf(aux,"/bin/%s", arg[0]);
					execv(aux, arg);
					sprintf(aux,"/usr/bin/%s", arg[0]);
					execv(aux, arg);
				}fim if*/
			if(strncmp("./",arg[0] , 2) != 0){
                path_cp = strdup(path);
                pointer_aux = strtok(path_cp, ":");
                while(pointer_aux != NULL) {
                    //printf("p2 = %s\n", pointer_aux);
                    if(pointer_aux[0] == ':') {
                        pointer_aux++;
                        pointer_aux = strtok(NULL, ":");
                    }
                    aux = malloc((strlen(pointer_aux)+strlen(arg[0]))*sizeof(char));
				    sprintf(aux,"%s/%s", pointer_aux, arg[0]);
				    execv(aux, args);
                    pointer_aux = strtok(NULL, ":");
                    free(aux);
                }
                free(path_cp);
				execv(arg[0], arg);
			    printf("%s: %s\n", args[0], strerror(errno));/*Se retornou, significa que houve erro*/
				return 0;
		    }
		else{/*FILHO*/
			/*redirecionando a saida do filho para a entrada do pai*/
			close(1);
			dup(pipefd[1]);
			close(pipefd[1]);
			close(pipefd[0]);
			execPipe(args, n-1, path);/*chamada recursiva*/
			return 0;
		}
	    }
    }
	return 0;
}
